﻿namespace Spectral
{
    using System;
    using Spectral.Operations;

    /// <summary>
    /// Provides creational methods for quaternions.
    /// </summary>
    public static class Quaternion
    {
        /// <summary>
        /// Creates a quaternion.
        /// </summary>
        /// <typeparam name="T">The element type.</typeparam>
        /// <param name="a">The first coefficient.</param>
        /// <param name="b">The second coefficient.</param>
        /// <param name="c">The third coefficient.</param>
        /// <param name="d">The fourth coefficient.</param>
        /// <returns>A Quaternion.</returns>
        public static Quaternion<T> Create<T>(T a, T b, T c, T d)
            where T : struct, IEquatable<T>, IComparable<T>
        {
            return new Quaternion<T>(a, b, c, d);
        }
    }

    /// <summary>
    /// Represents a quaternion.
    /// </summary>
    /// <typeparam name="T">The element type.</typeparam>
    public struct Quaternion<T> : IEquatable<Quaternion<T>>
        where T : struct, IEquatable<T>, IComparable<T>
    {
        /// <summary>
        /// The first coefficient.
        /// </summary>
        public readonly T A;

        /// <summary>
        /// The second coefficient.
        /// </summary>
        public readonly T B;

        /// <summary>
        /// The third coefficient.
        /// </summary>
        public readonly T C;

        /// <summary>
        /// The fourth coefficient.
        /// </summary>
        public readonly T D;

        /// <summary>
        /// Gets the real part of the equation.
        /// </summary>
        public T Real { get { return A; } }

        /// <summary>
        /// Gets the imaginary part of the equation.
        /// </summary>
        public Vector3<T> Imaginary { get { return new Vector3<T>(B, C, D); } }

        /// <summary>
        /// Initializes a new instance of the <see cref="Quaternion&lt;T&gt;"/> struct.
        /// </summary>
        /// <param name="real">The real part.</param>
        /// <param name="imaginary">The imaginary part.</param>
        public Quaternion(T real, Vector3<T> imaginary)
        {
            A = real;
            B = imaginary.X;
            C = imaginary.Y;
            D = imaginary.Z;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Quaternion&lt;T&gt;"/> struct.
        /// </summary>
        /// <param name="a">The first coefficient.</param>
        /// <param name="b">The second coefficient.</param>
        /// <param name="c">The third coefficient.</param>
        /// <param name="d">The fourth coefficient.</param>
        public Quaternion(T a, T b, T c, T d)
        {
            A = a;
            B = b;
            C = c;
            D = d;
        }

        /// <summary>
        /// Adds the specified value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>The result of the operation.</returns>
        public Quaternion<T> Add(Quaternion<T> value)
        {
            return new Quaternion<T>(A.Add(value.A), B.Add(value.B), C.Add(value.C), D.Add(value.D));
        }

        /// <summary>
        /// Subtracts the specified value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>The result of the operation.</returns>
        public Quaternion<T> Subtract(Quaternion<T> value)
        {
            return new Quaternion<T>(A.Add(value.A), B.Add(value.B), C.Add(value.C), D.Add(value.D));
        }

        /// <summary>
        /// Multiplies by the specified value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>The result of the operation.</returns>
        public Quaternion<T> Multiply(Quaternion<T> value)
        {
            return new Quaternion<T>(
                A.Multiply(value.A).Subtract(B.Multiply(value.B)).Subtract(C.Multiply(value.C)).Subtract(D.Multiply(value.D)),
                A.Multiply(value.B).Add(B.Multiply(value.A)).Add(C.Multiply(value.D)).Subtract(D.Multiply(value.C)),
                A.Multiply(value.C).Subtract(B.Multiply(value.D)).Add(C.Multiply(value.A)).Add(D.Multiply(value.B)),
                A.Multiply(value.D).Add(B.Multiply(value.C)).Subtract(C.Multiply(value.B)).Add(D.Multiply(value.A)));
        }

        /// <summary>
        /// Multiplies by the specified value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>The result of the operation.</returns>
        public Quaternion<T> Multiply(T value)
        {
            return new Quaternion<T>(A.Multiply(value), B.Multiply(value), C.Multiply(value), D.Multiply(value));
        }

        /// <summary>
        /// Divides by the specified value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>The result of the operation.</returns>
        public Quaternion<T> Divide(T value)
        {
            return new Quaternion<T>(A.Divide(value), B.Divide(value), C.Divide(value), D.Divide(value));
        }

        /// <summary>
        /// Calculates the negated version of this quaternion.
        /// </summary>
        /// <returns>The result of the operation.</returns>
        public Quaternion<T> Negate()
        {
            return new Quaternion<T>(A.Negate(), B.Negate(), C.Negate(), D.Negate());
        }

        /// <summary>
        /// Calculates the conjugate of this quaternion.
        /// </summary>
        /// <returns>The result of the operation.</returns>
        public Quaternion<T> Conjugate()
        {
            return new Quaternion<T>(A, B.Negate(), C.Negate(), D.Negate());
        }

        /// <summary>
        /// Calculates the inverse of this quaternion.
        /// </summary>
        /// <returns>The result of the operation.</returns>
        public Quaternion<T> Invert()
        {
            if (LengthSquared().Equals(Operator.Zero<T>()))
                return this;
            else
                return new Quaternion<T>(A, B.Negate(), C.Negate(), D.Negate()).Divide(LengthSquared());
        }

        /// <summary>
        /// Calculates the length of the quaternion.
        /// </summary>
        /// <returns>The squared of the quaternion.</returns>
        public T Length()
        {
            return LengthSquared().Sqrt();
        }

        /// <summary>
        /// Calculates the squared length of the quaternion.
        /// </summary>
        /// <returns>The squared length of the quaternion.</returns>
        public T LengthSquared()
        {
            return A.Multiply(A).Add(B.Multiply(B)).Add(C.Multiply(C)).Add(D.Multiply(D));
        }

        /// <summary>
        /// Casts this instance.
        /// </summary>
        /// <typeparam name="U">The new element type.</typeparam>
        /// <returns>A quaternion.</returns>
        public Quaternion<U> Cast<U>()
            where U : struct, IEquatable<U>, IComparable<U>
        {
            return new Quaternion<U>((U)Convert.ChangeType(A, typeof(U)), (U)Convert.ChangeType(B, typeof(U)), (U)Convert.ChangeType(C, typeof(U)), (U)Convert.ChangeType(D, typeof(U)));
        }

        /// <summary>
        /// Adds two quaternions together.
        /// </summary>
        /// <param name="left">The left side of the operation.</param>
        /// <param name="right">The right side of the operation.</param>
        /// <returns>The result of the operation.</returns>
        public static Quaternion<T> operator +(Quaternion<T> left, Quaternion<T> right)
        {
            return left.Add(right);
        }

        /// <summary>
        /// Subtracts one quaternion from another.
        /// </summary>
        /// <param name="left">The left side of the operation.</param>
        /// <param name="right">The right side of the operation.</param>
        /// <returns>The result of the operation.</returns>
        public static Quaternion<T> operator -(Quaternion<T> left, Quaternion<T> right)
        {
            return left.Subtract(right);
        }

        /// <summary>
        /// Multiplies two quaternions together.
        /// </summary>
        /// <param name="left">The left side of the operation.</param>
        /// <param name="right">The right side of the operation.</param>
        /// <returns>The result of the operation.</returns>
        public static Quaternion<T> operator *(Quaternion<T> left, Quaternion<T> right)
        {
            return left.Multiply(right);
        }

        /// <summary>
        /// Multiplies a quaternion by a scalar.
        /// </summary>
        /// <param name="left">The left side of the operation.</param>
        /// <param name="right">The right side of the operation.</param>
        /// <returns>The result of the operation.</returns>
        public static Quaternion<T> operator *(Quaternion<T> left, T right)
        {
            return left.Multiply(right);
        }

        /// <summary>
        /// Divides a quaternion by a scalar.
        /// </summary>
        /// <param name="left">The left side of the operation.</param>
        /// <param name="right">The right side of the operation.</param>
        /// <returns>The result of the operation.</returns>
        public static Quaternion<T> operator /(Quaternion<T> left, T right)
        {
            return left.Divide(right);
        }

        /// <summary>
        /// Indicates whether or not two quaternions are equal.
        /// </summary>
        /// <param name="left">The left side of the comparison.</param>
        /// <param name="right">The right side of the comparison.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator ==(Quaternion<T> left, Quaternion<T> right)
        {
            return left.Equals(right);
        }

        /// <summary>
        /// Indicates whether or not two quaternions are not equal.
        /// </summary>
        /// <param name="left">The left side of the comparison.</param>
        /// <param name="right">The right side of the comparison.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator !=(Quaternion<T> left, Quaternion<T> right)
        {
            return left.Equals(right);
        }

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        public bool Equals(Quaternion<T> other)
        {
            return A.Equals(other.A) && B.Equals(other.B) && C.Equals(other.C) && D.Equals(other.D);
        }

        /// <summary>
        /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
        /// <returns>
        ///   <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public override bool Equals(object obj)
        {
            return obj is Quaternion<T> ? Equals((Quaternion<T>)obj) : false;
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode()
        {
            return A.GetHashCode() ^ B.GetHashCode() ^ C.GetHashCode() ^ D.GetHashCode();
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return string.Format("A: {0}, B: {1}, C: {2}, D: {3}", A, B, C, D);
        }
    }
}
